package cn.line5.seam.webapp;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.Map.Entry;

import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import cn.line5.seam.component.Component;
import cn.line5.seam.contexts.UploadContext;
import cn.line5.seam.annotations.AjaxRemote;
import cn.line5.seam.annotations.Out;
import cn.line5.seam.annotations.Request;
import cn.line5.seam.cache.ComponentCache;
import cn.line5.seam.cache.FilterCache;
import cn.line5.seam.cache.PageCache;
import cn.line5.seam.cache.UploadConfigCache;
import cn.line5.seam.cache.pages.Page;
import cn.line5.seam.cache.pages.Param;
import cn.line5.seam.exception.SeamException;
import cn.line5.seam.filter.FilterServletContext;
import cn.line5.seam.filter.RemoteFilter;
import cn.line5.seam.util.ReflectUtil;

/**
 * <h1>SEAM请求处理Servlet类</h1>
 * 
 * @author Liu,Xiaojun
 * @since JDK1.6
 */
public class ActionServlet extends HttpServlet implements Servlet {

	private static final long serialVersionUID = 9162583400731171643L;

	private static final Logger log = LoggerFactory
			.getLogger(ActionServlet.class);

	@Override
	protected void service(HttpServletRequest request,
			HttpServletResponse response) throws ServletException, IOException {
		String path = request.getServletPath();
		log.info("request url : " + path);
		String id = path.substring(0, path.lastIndexOf(".")) + ".jsp";
		String returnPage = null;
		if (PageCache.pages.containsKey(id)) {
			Page page = PageCache.pages.get(id);
			// 如果设置了返回页面
			if (page.getReturnPage() != null
					&& page.getReturnPage().length() > 0)
				returnPage = page.getReturnPage();
			if (page.getAction() == null || page.getAction().length() <= 0
					|| page.getAction().split("\\.").length != 2) {
				throw new SeamException(
						"It's not defined action name or method name.");
			}
			if (page.getIsUpload() != null && page.getIsUpload()
					&& ServletFileUpload.isMultipartContent(request)) {
				UploadContext.setUploadResult(new UploadFiles(request, page
						.getUploadPath() != null ? page.getUploadPath()
						: UploadConfigCache.uploadPath, page.getFileType())
						.upload());
			}
			String[] action = page.getAction().split("\\.");
			if (!ComponentCache.componentClass.containsKey(action[0])) {
				throw new SeamException(
						"Can't find the action class by named '" + action[0]
								+ "'.");
			}
			FilterServletContext context = new FilterServletContext();
			context.setComponentName(action[0]);
			context.setMethodName(action[1]);
			context.setRequestType(FilterServletContext.REQUEST_TYPE_PAGE);
			context.setRequest(request);
			context.setResponse(response);
			try {
				if (!RemoteFilter.createRemoteFilter().filter(context))
					return;
			} catch (Exception e) {
				throw new SeamException("Can't use the RemoteFilter by named '"
						+ FilterCache.filterClass + "'.", e);
			}
			Component component = ComponentCache.componentClass.get(action[0]);
			Object o = component.getComponent();
			// 写入参数
			if (page.getList() != null && page.getList().size() > 0) {
				for (Param p : page.getList()) {
					if (component.getParameterField().containsKey(p.getName())) {
						// 如果不是String类型
						if (p.getType() != null
								&& !"java.lang.String".equals(p.getType())
								&& !"String".equals(p.getType())) {
							try {
								Field[] fields = Class.forName(p.getType())
										.getDeclaredFields();
								if (fields != null) {
									Object vo = Class.forName(p.getType())
											.newInstance();
									for (Field field : fields) {
										ReflectUtil
												.setFieldValue(
														field.getName(),
														vo,
														field.getType()
																.isArray() ? getParameterValues(
																field.getName(),
																request)
																: getParameter(
																		field.getName(),
																		request));
									}
									ReflectUtil.setFieldValue(p.getName(), o,
											vo);
								}
							} catch (ClassNotFoundException e) {
								throw new SeamException(
										"Can't find the class '" + p.getType()
												+ "'.", e);
							} catch (InstantiationException e) {
								throw new SeamException("The class '"
										+ p.getType() + "' instance error.", e);
							} catch (IllegalAccessException e) {
								throw new SeamException("The class '"
										+ p.getType() + "' instance error.", e);
							}
						} else {
							try {
								ReflectUtil
										.setFieldValue(
												p.getName(),
												o,
												component
														.getClazz()
														.getDeclaredField(
																p.getName())
														.getType().isArray() ? getParameterValues(
														p.getName(), request)
														: getParameter(
																p.getName(),
																request));
							} catch (NoSuchFieldException e) {
								throw new SeamException("The field named '"
										+ p.getName()
										+ "' is not defined in the class "
										+ component.getClazz().getName() + ".",
										e);
							}
						}
					} else {
						throw new SeamException("The Param field '"
								+ p.getName()
								+ "' is not defined in the class "
								+ component.getClazz().getName() + ".");
					}
				}
			}
			// 写入request对象
			if (component.getRequestField() != null
					&& component.getRequestField().size() > 0) {
				Iterator<Entry<String, Request>> t = component
						.getRequestField().entrySet().iterator();
				while (t.hasNext()) {
					String requestFieldName = t.next().getKey();
					try {
						Field f = component.getClazz().getDeclaredField(
								requestFieldName);
						if (!f.getType()
								.getName()
								.equals("javax.servlet.http.HttpServletRequest")) {
							throw new SeamException(
									"The Request field '"
											+ requestFieldName
											+ "' must defined the type 'javax.servlet.http.HttpServletRequest'");
						}
						f.setAccessible(true);
						ReflectUtil.setFieldValue(requestFieldName, o, request);
					} catch (NoSuchFieldException e) {
						throw new SeamException("Can't find the field named '"
								+ requestFieldName + "' in the "
								+ component.getClazz().getName() + ".", e);
					}
				}
			}
			// 执行方法
			try {
				Method m = component.getClazz().getMethod(action[1]);
				if (m.getAnnotation(AjaxRemote.class) != null
						|| m.getReturnType() != void.class) { // 如果含有@AjaxRemote抛出异常；如果方法有返回值抛出异常；
					throw new SeamException(
							"Can't find the seam method by named '" + action[1]
									+ "' in the "
									+ component.getClazz().getName() + ".");
				}
				m.invoke(o);
			} catch (SecurityException e) {
				throw new SeamException("Can't find the method by named '"
						+ action[1] + "' in the "
						+ component.getClazz().getName() + ".", e);
			} catch (IllegalArgumentException e) {
				throw new SeamException("The method running error by named '"
						+ action[1] + "' in the "
						+ component.getClazz().getName() + ".", e);
			} catch (NoSuchMethodException e) {
				throw new SeamException("Can't find the method by named '"
						+ action[1] + "' in the "
						+ component.getClazz().getName() + ".", e);
			} catch (IllegalAccessException e) {
				throw new SeamException("The method running error by named '"
						+ action[1] + "' in the "
						+ component.getClazz().getName() + ".", e);
			} catch (InvocationTargetException e) {
				throw new SeamException("The method running error by named '"
						+ action[1] + "' in the "
						+ component.getClazz().getName() + ".", e);
			}
			// 写入输出
			if (component.getOutField() != null
					&& component.getOutField().size() > 0) {
				Iterator<Entry<String, Out>> t = component.getOutField()
						.entrySet().iterator();
				while (t.hasNext()) {
					String outFieldName = t.next().getKey();
					try {
						Field f = component.getClazz().getDeclaredField(
								outFieldName);
						f.setAccessible(true);
						Object outObject = f.get(o);
						if (outObject != null)
							request.setAttribute(outFieldName, outObject);
					} catch (IllegalArgumentException e) {
						throw new SeamException("The Out field '"
								+ outFieldName + "' write error in the class "
								+ component.getClazz().getName() + ".", e);
					} catch (SecurityException e) {
						throw new SeamException("The Out field '"
								+ outFieldName + "' write error in the class "
								+ component.getClazz().getName() + ".", e);
					} catch (IllegalAccessException e) {
						throw new SeamException("The Out field '"
								+ outFieldName + "' write error in the class "
								+ component.getClazz().getName() + ".", e);
					} catch (NoSuchFieldException e) {
						throw new SeamException("The Out field '"
								+ outFieldName + "' write error in the class "
								+ component.getClazz().getName() + ".", e);
					}
				}
			}
			// 如果是上传文件，需要清空上下文内容
			if (page.getIsUpload() != null && page.getIsUpload()
					&& ServletFileUpload.isMultipartContent(request))
				UploadContext.clearAll();
		}
		// 如果设置了返回页面
		if (returnPage != null && returnPage.length() > 0) {
			// 如果设置了参数，拼接参数
			while (returnPage.lastIndexOf("{") < returnPage.lastIndexOf("}")) {
				String param = returnPage.substring(
						returnPage.lastIndexOf("{") + 1,
						returnPage.lastIndexOf("}"));
				returnPage = returnPage.replace("{" + param + "}",
						request.getAttribute(param) != null ? request
								.getAttribute(param).toString() : "");
			}
			response.sendRedirect(returnPage);
		} else
			getServletContext().getRequestDispatcher(id).forward(request,
					response);
	}

	private String[] getParameterValues(String name, HttpServletRequest request) {
		return request.getParameterValues(name) == null ? UploadContext
				.getParameterValues(name) : request.getParameterValues(name);
	}

	private String getParameter(String name, HttpServletRequest request) {
		return request.getParameter(name) == null ? UploadContext
				.getParameter(name) : request.getParameter(name);
	}
}